/*
 * Copyright 2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sf.jsfcomp.chartcreator.utils;

import java.awt.BasicStroke;
import java.awt.Color;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Iterator;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import net.sf.jsfcomp.chartcreator.model.ChartAxisData;
import net.sf.jsfcomp.chartcreator.model.ChartData;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.block.BlockContainer;
import org.jfree.chart.block.BorderArrangement;
import org.jfree.chart.labels.StandardCategoryToolTipGenerator;
import org.jfree.chart.labels.StandardPieToolTipGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.PiePlot3D;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.urls.StandardCategoryURLGenerator;
import org.jfree.chart.urls.StandardPieURLGenerator;
import org.jfree.chart.urls.StandardXYURLGenerator;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.IntervalCategoryDataset;
import org.jfree.data.general.PieDataset;
import org.jfree.data.statistics.BoxAndWhiskerXYDataset;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.data.xy.OHLCDataset;
import org.jfree.data.xy.WindDataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYZDataset;
import org.jfree.ui.HorizontalAlignment;
import org.jfree.ui.RectangleEdge;

/**
 * @author Cagatay Civici (latest modification by $Author: mustafaulu.net $)
 * @since 1.2.0
 */
public class ChartUtils {

    private static String passthruImgAttributes[] = { "alt", "styleClass",
            "onclick", "ondblclick", "onmousedown", "onmouseup", "onmouseover",
            "onmousemove", "onmouseout", "onkeypress", "onkeydown", "onkeyup",
            "usemap", };

    public static void renderPassThruImgAttributes(ResponseWriter writer,
            UIComponent component) throws IOException {
        for (int i = 0; i < passthruImgAttributes.length; i++) {
            Object value = component.getAttributes().get(
                    passthruImgAttributes[i]);
            if (value != null) {
                writer.writeAttribute(passthruImgAttributes[i], value, null);
            }
        }
        // title attribute overlaps with the chart title so renamed to imgTitle
        // to define img tag's title
        if (component.getAttributes().get("imgTitle") != null) {
            writer.writeAttribute("title", component.getAttributes().get(
                    "imgTitle"), null);
        }
    }

    public static PlotOrientation getPlotOrientation(String orientation) {
        if (orientation.equalsIgnoreCase("horizontal")) {
            return PlotOrientation.HORIZONTAL;
        } else if (orientation.equalsIgnoreCase("vertical")) {
            return PlotOrientation.VERTICAL;
        } else {
            throw new RuntimeException("Unsupported plot orientation:"
                    + orientation);
        }
    }

    public static RectangleEdge getLegendPosition(String position) {
        if (position.equalsIgnoreCase("bottom")) {
            return RectangleEdge.BOTTOM;
        } else if (position.equalsIgnoreCase("left")) {
            return RectangleEdge.LEFT;
        } else if (position.equalsIgnoreCase("right")) {
            return RectangleEdge.RIGHT;
        } else if (position.equalsIgnoreCase("top")) {
            return RectangleEdge.TOP;
        } else {
            throw new RuntimeException("Unsupported legend position:"
                    + position);
        }
    }

    public static Color getColor(String color) {
        // HTML colors (#FFFFFF format)
        if (color.startsWith("#")) {
            return new Color(Integer.parseInt(color.substring(1), 16));
        }

        // Colors by name
        if (color.equalsIgnoreCase("black")) {
            return Color.black;
        }
        if (color.equalsIgnoreCase("gray")) {
            return Color.gray;
        }
        if (color.equalsIgnoreCase("yellow")) {
            return Color.yellow;
        }
        if (color.equalsIgnoreCase("green")) {
            return Color.green;
        }
        if (color.equalsIgnoreCase("blue")) {
            return Color.blue;
        }
        if (color.equalsIgnoreCase("red")) {
            return Color.red;
        }
        if (color.equalsIgnoreCase("orange")) {
            return Color.orange;
        }
        if (color.equalsIgnoreCase("cyan")) {
            return Color.cyan;
        }
        if (color.equalsIgnoreCase("magenta")) {
            return Color.magenta;
        }
        if (color.equalsIgnoreCase("darkgray")) {
            return Color.darkGray;
        }
        if (color.equalsIgnoreCase("lightgray")) {
            return Color.lightGray;
        }
        if (color.equalsIgnoreCase("pink")) {
            return Color.pink;
        }
        if (color.equalsIgnoreCase("white")) {
            return Color.white;
        }

        throw new RuntimeException("Unsupported chart color:" + color);
    }

    public static String resolveContentType(String output) {
        if (output.equalsIgnoreCase("png")) {
            return "img/png";
        } else if (output.equalsIgnoreCase("jpeg")) {
            return "img/jpeg";
        } else {
            throw new RuntimeException("Unsupported output format:" + output);
        }
    }

    // Creates the chart with the given chart data
    public static JFreeChart createChartWithType(ChartData chartData) {
        JFreeChart chart = null;
        Object datasource = chartData.getDatasource();
        if (datasource instanceof PieDataset) {
            chart = createChartWithPieDataSet(chartData);
        } else if (datasource instanceof CategoryDataset) {
            chart = createChartWithCategoryDataSet(chartData);
        } else if (datasource instanceof XYDataset) {
            chart = createChartWithXYDataSet(chartData);
        } else {
            throw new RuntimeException("Unsupported chart type");
        }

        if (chartData.getLegendFontSize() > 0) {
            chart.getLegend().setItemFont(
                    LegendTitle.DEFAULT_ITEM_FONT.deriveFont(chartData
                            .getLegendFontSize()));
        }

        if (!chartData.isLegendBorder()) {
            chart.getLegend().setBorder(0, 0, 0, 0);
        }

        return chart;
    }

    public static void setGeneralChartProperties(JFreeChart chart,
            ChartData chartData) {
        chart
                .setBackgroundPaint(ChartUtils.getColor(chartData
                        .getBackground()));
        chart.getPlot().setBackgroundPaint(
                ChartUtils.getColor(chartData.getForeground()));
        chart.setTitle(chartData.getTitle());
        chart.setAntiAlias(chartData.isAntialias());

        // Alpha transparency (100% means opaque)
        if (chartData.getAlpha() < 100) {
            chart.getPlot().setForegroundAlpha(
                    (float) chartData.getAlpha() / 100);
        }
    }

    public static JFreeChart createChartWithCategoryDataSet(ChartData chartData) {
        JFreeChart chart = null;
        PlotOrientation plotOrientation = ChartUtils
                .getPlotOrientation(chartData.getOrientation());

        CategoryDataset dataset = (CategoryDataset) chartData.getDatasource();
        String type = chartData.getType();
        String xAxis = chartData.getXlabel();
        String yAxis = chartData.getYlabel();
        boolean is3d = chartData.isChart3d();
        boolean legend = chartData.isLegend();

        if (type.equalsIgnoreCase("bar")) {
            if (is3d == true) {
                chart = ChartFactory.createBarChart3D("", xAxis, yAxis,
                        dataset, plotOrientation, legend, true, false);
            } else {
                chart = ChartFactory.createBarChart("", xAxis, yAxis, dataset,
                        plotOrientation, legend, true, false);
            }
            setBarOutline(chart, chartData.isBarOutline());
        } else if (type.equalsIgnoreCase("stackedbar")) {
            if (is3d == true) {
                chart = ChartFactory.createStackedBarChart3D("", xAxis, yAxis,
                        dataset, plotOrientation, legend, true, false);
            } else {
                chart = ChartFactory.createStackedBarChart("", xAxis, yAxis,
                        dataset, plotOrientation, legend, true, false);
            }
            setBarOutline(chart, chartData.isBarOutline());
        } else if (type.equalsIgnoreCase("line")) {
            if (is3d == true) {
                chart = ChartFactory.createLineChart3D("", xAxis, yAxis,
                        dataset, plotOrientation, legend, true, false);
            } else {
                chart = ChartFactory.createLineChart("", xAxis, yAxis, dataset,
                        plotOrientation, legend, true, false);
            }
        } else if (type.equalsIgnoreCase("area")) {
            chart = ChartFactory.createAreaChart("", xAxis, yAxis, dataset,
                    plotOrientation, legend, true, false);
        } else if (type.equalsIgnoreCase("stackedarea")) {
            chart = ChartFactory.createStackedAreaChart("", xAxis, yAxis,
                    dataset, plotOrientation, legend, true, false);
        } else if (type.equalsIgnoreCase("waterfall")) {
            chart = ChartFactory.createWaterfallChart("", xAxis, yAxis,
                    dataset, plotOrientation, legend, true, false);
        } else if (type.equalsIgnoreCase("gantt")) {
            chart = ChartFactory.createGanttChart("", xAxis, yAxis,
                    (IntervalCategoryDataset) dataset, legend, true, false);
        }

        CategoryPlot plot = (CategoryPlot) chart.getCategoryPlot();

        plot.setOutlineVisible(chartData.isOutline());
        plot.setDomainGridlinesVisible(chartData.isDomainGridLines());
        plot.setRangeGridlinesVisible(chartData.isRangeGridLines());

        if (chartData.getGenerateMap() != null) {
            plot.getRenderer().setBaseItemURLGenerator(
                    new StandardCategoryURLGenerator(""));
        }

        if (chartData.getDataTooltipFormat() != null) {
            NumberFormat formatter = new DecimalFormat(chartData
                    .getDataTooltipFormat());

            plot
                    .getRenderer()
                    .setToolTipGenerator(
                            new StandardCategoryToolTipGenerator(
                                    StandardCategoryToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT_STRING,
                                    formatter));
        }

        int seriesCount = plot.getDataset().getColumnCount();
        if (chartData.getLineStokeWidth() > 0) {
            for (int index = 0; index <= seriesCount; index++) {
                plot.getRenderer().setSeriesStroke(index,
                        new BasicStroke(chartData.getLineStokeWidth()));
            }
        }

        setCategorySeriesColors(chart, chartData);

        setCategoryExtensions(chart, chartData);

        return chart;
    }

    public static JFreeChart createChartWithPieDataSet(ChartData chartData) {
        JFreeChart chart = null;

        PieDataset dataset = (PieDataset) chartData.getDatasource();
        String type = chartData.getType();

        boolean legend = chartData.isLegend();

        if (chartData.getLegendPosition() != null) {
            legend = false;
        }

        PiePlot plot = null;
        if (type.equalsIgnoreCase("pie")) {
            if (chartData.isChart3d()) {
                chart = ChartFactory.createPieChart3D("", dataset, legend,
                        true, false);
                plot = (PiePlot) chart.getPlot();
                ((PiePlot3D) plot)
                        .setDepthFactor((float) chartData.getDepth() / 100);
            } else {
                chart = ChartFactory.createPieChart("", dataset, legend, true,
                        false);
                plot = (PiePlot) chart.getPlot();
                if (chartData.getExplode() != 0.0) {
                    // Iterate over dataset to specify explode percent for keys.
                    Comparable key = null;
                    Iterator iterator = dataset.getKeys().iterator();
                    while (iterator.hasNext()) {
                        key = (Comparable) iterator.next();
                        plot.setExplodePercent(key, chartData.getExplode());
                    }
                }
            }
        } else if (type.equalsIgnoreCase("ring")) {
            chart = ChartFactory.createRingChart("", dataset, legend, true,
                    false);
            plot = (PiePlot) chart.getPlot();
        }

        plot.setStartAngle((float) chartData.getStartAngle());
        plot.setOutlineVisible(chartData.isOutline());

        if (chartData.getPieLabelBackground() != null) {
            plot.setLabelBackgroundPaint(ChartUtils.getColor(chartData
                    .getPieLabelBackground()));
        }

        if (chartData.getPieLabelOutline() != null) {
            plot.setLabelOutlinePaint(ChartUtils.getColor(chartData
                    .getPieLabelOutline()));
        }

        if (chartData.getGenerateMap() != null) {
            plot.setURLGenerator(new StandardPieURLGenerator(""));
        }

        if (chartData.getDataTooltipFormat() != null) {
            NumberFormat formatter = new DecimalFormat(chartData
                    .getDataTooltipFormat());

            plot.setToolTipGenerator(new StandardPieToolTipGenerator(
                    StandardPieToolTipGenerator.DEFAULT_SECTION_LABEL_FORMAT,
                    formatter, formatter));
        }

        if (chartData.getLegendPosition() != null) {
            LegendTitle legendBox = new LegendTitle(chart.getPlot());

            BlockContainer wrapper = new BlockContainer(new BorderArrangement());
            wrapper.setFrame(new BlockBorder(1.0, 1.0, 1.0, 1.0));

            BlockContainer items = legendBox.getItemContainer();
            items.setPadding(2, 2, 2, 2);
            wrapper.add(items);
            legendBox.setWrapper(wrapper);

            legendBox.setPosition(ChartUtils.getLegendPosition(chartData
                    .getLegendPosition()));
            if (legendBox.getPosition().equals(RectangleEdge.TOP)
                    || legendBox.getPosition().equals(RectangleEdge.BOTTOM)) {
                legendBox.setHorizontalAlignment(HorizontalAlignment.CENTER);
            } else {
                legendBox.setHorizontalAlignment(HorizontalAlignment.LEFT);
            }

            chart.addSubtitle(legendBox);
        }

        setPieSectionColors(chart, chartData);

        return chart;
    }

    public static JFreeChart createChartWithXYDataSet(ChartData chartData) {
        XYDataset dataset = (XYDataset) chartData.getDatasource();
        String type = chartData.getType();
        String xAxis = chartData.getXlabel();
        String yAxis = chartData.getYlabel();
        boolean legend = chartData.isLegend();

        JFreeChart chart = null;
        PlotOrientation plotOrientation = ChartUtils
                .getPlotOrientation(chartData.getOrientation());

        if (type.equalsIgnoreCase("timeseries")) {
            chart = ChartFactory.createTimeSeriesChart("", xAxis, yAxis,
                    dataset, legend, true, false);
        } else if (type.equalsIgnoreCase("xyline")) {
            chart = ChartFactory.createXYLineChart("", xAxis, yAxis, dataset,
                    plotOrientation, legend, true, false);
        } else if (type.equalsIgnoreCase("polar")) {
            chart = ChartFactory.createPolarChart("", dataset, legend, true,
                    false);
        } else if (type.equalsIgnoreCase("scatter")) {
            chart = ChartFactory.createScatterPlot("", xAxis, yAxis, dataset,
                    plotOrientation, legend, true, false);
        } else if (type.equalsIgnoreCase("xyarea")) {
            chart = ChartFactory.createXYAreaChart("", xAxis, yAxis, dataset,
                    plotOrientation, legend, true, false);
        } else if (type.equalsIgnoreCase("xysteparea")) {
            chart = ChartFactory.createXYStepAreaChart("", xAxis, yAxis,
                    dataset, plotOrientation, legend, true, false);
        } else if (type.equalsIgnoreCase("xystep")) {
            chart = ChartFactory.createXYStepChart("", xAxis, yAxis, dataset,
                    plotOrientation, legend, true, false);
        } else if (type.equalsIgnoreCase("bubble")) {
            chart = ChartFactory.createBubbleChart("", xAxis, yAxis,
                    (XYZDataset) dataset, plotOrientation, legend, true, false);
        } else if (type.equalsIgnoreCase("candlestick")) {
            chart = ChartFactory.createCandlestickChart("", xAxis, yAxis,
                    (OHLCDataset) dataset, legend);
        } else if (type.equalsIgnoreCase("boxandwhisker")) {
            chart = ChartFactory.createBoxAndWhiskerChart("", xAxis, yAxis,
                    (BoxAndWhiskerXYDataset) dataset, legend);
        } else if (type.equalsIgnoreCase("highlow")) {
            chart = ChartFactory.createHighLowChart("", xAxis, yAxis,
                    (OHLCDataset) dataset, legend);
        } else if (type.equalsIgnoreCase("histogram")) {
            chart = ChartFactory.createHistogram("", xAxis, yAxis,
                    (IntervalXYDataset) dataset, plotOrientation, legend, true,
                    false);
        } else if (type.equalsIgnoreCase("wind")) {
            chart = ChartFactory.createWindPlot("", xAxis, yAxis,
                    (WindDataset) dataset, legend, true, false);
        }

        Plot plot = chart.getPlot();
        plot.setOutlineVisible(chartData.isOutline());
        if (plot instanceof XYPlot) {
            ((XYPlot) plot).setDomainGridlinesVisible(chartData
                    .isDomainGridLines());
            ((XYPlot) plot).setRangeGridlinesVisible(chartData
                    .isRangeGridLines());

            if (chartData.getGenerateMap() != null) {
                chart.getXYPlot().getRenderer().setURLGenerator(
                        new StandardXYURLGenerator(""));
            }
        }

        setXYSeriesColors(chart, chartData);

        setXYExtensions(chart, chartData);

        return chart;
    }

    /**
     * Series coloring Plot has no getRenderer so two methods for each plot
     * type(categoryplot and xyplot)
     */
    public static void setCategorySeriesColors(JFreeChart chart,
            ChartData chartData) {
        if (chart.getPlot() instanceof CategoryPlot) {
            CategoryPlot plot = (CategoryPlot) chart.getPlot();
            if (chartData.getColors() != null) {
                String[] colors = chartData.getColors().split(",");
                for (int i = 0; i < colors.length; i++) {
                    plot.getRenderer().setSeriesPaint(i,
                            ChartUtils.getColor(colors[i].trim()));
                }
            }
        }
    }

    public static void setXYSeriesColors(JFreeChart chart, ChartData chartData) {
        if (chart.getPlot() instanceof XYPlot && chartData.getColors() != null) {
            XYPlot plot = (XYPlot) chart.getPlot();
            String[] colors = chartData.getColors().split(",");
            for (int i = 0; i < colors.length; i++) {
                plot.getRenderer().setSeriesPaint(i,
                        ChartUtils.getColor(colors[i].trim()));
            }
        }
    }

    public static void setPieSectionColors(JFreeChart chart, ChartData chartData) {
        if (chartData.getColors() != null) {
            String[] colors = chartData.getColors().split(",");
            for (int i = 0; i < colors.length; i++) {
                // ((PiePlot)chart.getPlot()).setSectionPaint(new Integer(i),
                // ChartUtils.getColor(colors[i].trim())); does not work
                ((PiePlot) chart.getPlot()).setSectionPaint(i, ChartUtils
                        .getColor(colors[i].trim()));
            }
        }
    }

    /**
     * Sets the outline of the bars
     */
    public static void setBarOutline(JFreeChart chart, boolean barOutline) {
        CategoryPlot plot = (CategoryPlot) chart.getPlot();
        BarRenderer barrenderer = (BarRenderer) plot.getRenderer();
        barrenderer.setDrawBarOutline(barOutline);
    }

    /**
     * XY charts extensions
     */
    public static void setXYExtensions(JFreeChart chart, ChartData chartData) {
        int axisIndex = 1;
        for (Iterator it = chartData.getExtensions().iterator(); it.hasNext();) {
            Object extension = it.next();
            if (extension instanceof ChartAxisData) {
                ChartAxisData chartAxisData = (ChartAxisData) extension;
                ChartAxisUtils.createXYSeriesAxis(chart, chartAxisData,
                        axisIndex);
                axisIndex++;
            }
        }
    }

    /**
     * Category charts extensions
     */
    public static void setCategoryExtensions(JFreeChart chart,
            ChartData chartData) {
        int axisIndex = 1;
        for (Iterator it = chartData.getExtensions().iterator(); it.hasNext();) {
            Object extension = it.next();
            if (extension instanceof ChartAxisData) {
                ChartAxisData chartAxisData = (ChartAxisData) extension;
                ChartAxisUtils.createCategorySeriesAxis(chart, chartAxisData,
                        axisIndex);
                axisIndex++;
            }
        }
    }

    public static boolean useServlet(FacesContext facesContext) {
        String value = facesContext.getExternalContext().getInitParameter(
                ChartConstants.GENERATOR_PARAM);
        if (value != null) {
            return Boolean.valueOf(value).booleanValue();
        } else {
            return true;
        }
    }
}